package com.example.tool.otherui;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.example.tool.entity.TableViewEnum;
import com.example.tool.es.Es7Client;
import com.example.tool.mysql.JdbcUtils;
import com.jfoenix.controls.*;
import io.datafx.controller.ViewController;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import javafx.stage.Modality;
import javafx.stage.Window;
import javafx.util.Callback;
import javafx.util.Pair;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.lang.Math.round;

@ViewController(value = "/fxml/ui/tableViewLog.fxml", title = "内容展示panel")
public class TableViewQueryLogController {

    @FXML
    private JFXRadioButton platform_all;
    @FXML
    private JFXRadioButton platform_meituan;

    @FXML
    private JFXRadioButton platform_ele;
    @FXML
    private JFXTimePicker startTimePicker;

    @FXML
    private JFXTimePicker endTimePicker;

    @FXML
    private JFXDatePicker runDatePicker;

    @FXML
    private JFXComboBox<Label> taskTypeComboBox;


    @FXML
    private JFXComboBox<Label> finishComboBox;


    @FXML
    private JFXComboBox<Label> statusComboBox;


    @FXML
    private JFXComboBox<String> sourceComboBox;

    //
    @FXML
    private JFXComboBox<String> serviceComboBox;

    @FXML
    private JFXButton searchTable;
    @FXML
    private JFXTextField keywordText;
    @FXML
    private JFXComboBox<Label> keywordComboBox;

    @FXML
    private VBox tableViewId;

    @FXML
    private VBox paginationId;

    private ThreadLocal<Date> startTimeThreadLocal = new ThreadLocal<>();

    private static final Map<String, Integer> taskTypeMap = new HashMap<>();
    private static final Map<String, Integer> finishMap = new HashMap<>();
    private static final Map<String, Integer> statusMap = new HashMap<>();
    private static final Map<String, String> keywordMap = new HashMap<>();
    private static final Comparator<Terms.Bucket> bucketComparator = Comparator.comparingLong((Terms.Bucket o) -> o.getDocCount()).reversed();


    private final Function<JSONObject, Task> taskMapper = taskObj -> {
        try {
            Task t = new Task();
            JSONArray steps = taskObj.getJSONArray("steps");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            for (int i = 0; i < steps.size(); i++) {
                JSONObject step = steps.getJSONObject(i);
                Step st = new Step();
                st.setTaskId(step.getString("taskId"));
                st.setServiceName(step.getString("serviceName"));
                st.setRemark(step.getString("remark"));
                st.setClassName(step.getString("className"));
                st.setMethodName(step.getString("methodName"));
                st.setStep(step.getInteger("step"));
                st.setStatus(step.getInteger("status"));
                st.setStage(step.getInteger("stage"));


                st.setCost(step.getInteger("cost"));
                st.setFinish(step.getBoolean("finish"));

                JSONArray stepLogs = step.getJSONArray("stepLogs");
                String endTime = step.getString("endTime");
                String startTime = step.getString("startTime");
                if (StringUtils.isEmpty(startTime)) {
                    startTime = stepLogs.getJSONObject(0).getString("startTime");
                }
                if (StringUtils.isEmpty(endTime)) {
                    endTime = stepLogs.getJSONObject(0).getString("endTime");
                }
                st.setBegin(simpleDateFormat.parse(startTime));
                st.setEnd(simpleDateFormat.parse(endTime));

                List<StepNode> stepNodeList = new ArrayList<>();
                for (int i1 = 0; i1 < stepLogs.size(); i1++) {
                    JSONObject stepLog = stepLogs.getJSONObject(i1);
                    StepNode stepNode = new StepNode();
                    stepNode.setCost(stepLog.getInteger("cost"));
                    stepNode.setLogId(stepLog.getString("logId"));
                    stepNode.setTaskId(stepLog.getString("taskId"));
                    stepNode.setFinish(stepLog.getBoolean("finish"));
                    stepNode.setStatus(stepLog.getInteger("status"));
                    if (stepLog.containsKey("result")) {
                        stepNode.setResult(stepLog.getString("result"));
                    } else {
                        stepNode.setResult("success");
                    }
                    stepNodeList.add(stepNode);
                }
                st.setStepNodes(stepNodeList);
                t.addStep(st);
            }
            return t;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    };

    @Data
    private class Step {
        private String serviceName;
        private String className;

        private String methodName;

        private String taskId;

        private String remark;

        private Integer step;
        private Integer status;

        private Integer stage;

        private Date begin;

        private Date end;

        private Integer cost;

        private boolean finish;

        private List<StepNode> stepNodes;
    }

    @Data
    private class StepNode {
        private String taskId;
        private String logId;
        private Integer status;
        private boolean finish;
        private Integer cost;
        private String result;
    }

    @Data
    static class Task {
        List<Step> stepList;
        private String source;
        private Integer taskType;

        public void addStep(Step st) {
            if (stepList == null) {
                stepList = new ArrayList<>();
            }
            stepList.add(st);
        }
    }

    @Data
    static class StepNodeEntity {
        private List<StepNode> stepNodeList;
        private Integer page;
        private boolean next;

        public int nextPage() {
            return next ? page + 1 : page - 1;
        }
    }


    private static final Function<JFXComboBox<String>, String> comboBoxValFun = stringJFXComboBox -> {
        String value = stringJFXComboBox.getValue();
        if (StringUtils.equals(value, "--请选择--")) {
            return null;
        }
        if (StringUtils.isNotEmpty(value)) {
            value = value.split("\\(")[0];
        }
        return value;
    };

    private static final Function<LocalDate, Date> localDateDateFunction = localDate -> {
        String value = localDate.toString();
        try {
            return new SimpleDateFormat("yyyy-MM-dd").parse(value);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    };
    private static final Function<Pair<Date, LocalTime>, Date> localTimeFunction = pair -> {
        Date date = pair.getKey();
        String time = pair.getValue().toString();
        String timeStr = new SimpleDateFormat("yyyy-MM-dd").format(date) + " " + time;
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(timeStr);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    };

    static {
        taskTypeMap.put("历史推广", 1);
        taskTypeMap.put("实时推广", 2);
        taskTypeMap.put("站外支付", 5);
        taskTypeMap.put("站外授权", 6);
        taskTypeMap.put("菜品清洗", 8);
        finishMap.put("--请选择--", null);
        finishMap.put("完成", 1);
        finishMap.put("执行中", 0);
        statusMap.put("--请选择--", null);
        statusMap.put("成功", 1);
        statusMap.put("失败", 0);

        keywordMap.put("账号", "accountId");
        keywordMap.put("店铺id", "shopIds");
        keywordMap.put("任务id", "id");
        keywordMap.put("父任务id", "parentTaskId");
    }

    @PostConstruct
    public void init() throws IOException {
        final ToggleGroup toggleGroup = new ToggleGroup();
        platform_meituan.setToggleGroup(toggleGroup);
        platform_all.setToggleGroup(toggleGroup);
        platform_ele.setToggleGroup(toggleGroup);
        platform_all.setSelected(true);

        startTimePicker.set24HourView(true);
        endTimePicker.set24HourView(true);
        startTimePicker.setConverter(new LocalTimeStringConverter2());
        endTimePicker.setConverter(new LocalTimeStringConverter2());
        startTimePicker.setValue(LocalTime.of(0, 0));
        endTimePicker.setValue(LocalTime.of(23, 59));
        runDatePicker.setValue(LocalDate.now());
        keywordComboBox.setValue(keywordComboBox.getItems().get(0));
        Consumer<Pair<ParsedStringTerms, JFXComboBox<String>>> initComboBoxFun = tuple3 -> {
            JFXComboBox<String> comboBox = tuple3.getValue();
            ObservableList<String> children = comboBox.getItems();
            children.clear();
            children.add("--请选择--");
            ParsedStringTerms sourceGroup = tuple3.getKey();
            for (Terms.Bucket bucket : sourceGroup.getBuckets().stream()
                    .sorted(bucketComparator).collect(Collectors.toList())) {
                String source = bucket.getKeyAsString();
                long docCount = bucket.getDocCount();
                children.add(source + "(" + docCount + ")");
            }
        };

        taskTypeComboBox.setOnAction(event -> {
            Label taskTypeValue = taskTypeComboBox.getValue();
            Date date = localDateDateFunction.apply(runDatePicker.getValue());
            JSONObject headers = Es7Client.statisticsHeader(taskTypeMap.get(taskTypeValue.getText()), date, "dkd_chain_monitor_task_record_" + DateUtil.format(date, "YYYY_MM"));
            initComboBoxFun.accept(new Pair<>((ParsedStringTerms) headers.get("source"), sourceComboBox));
            initComboBoxFun.accept(new Pair<>((ParsedStringTerms) headers.get("serviceName"), serviceComboBox));
        });
        statusComboBox.setValue(statusComboBox.getItems().get(0));
        finishComboBox.setValue(finishComboBox.getItems().get(0));
        //taskTypeComboBox.setValue(taskTypeComboBox.getItems().get(0));
        searchTable.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                boolean all = platform_all.isSelected();
                boolean ele = platform_ele.isSelected();
                Integer platform = all ? null : (ele ? 2 : 1);
                String taskTypeValue = taskTypeComboBox.getValue().getText();
                Integer taskType = taskTypeMap.get(taskTypeValue);
                Integer finish = finishMap.get(finishComboBox.getValue().getText());
                Integer status = statusMap.get(statusComboBox.getValue().getText());
                String source = comboBoxValFun.apply(sourceComboBox);
                String serviceName = comboBoxValFun.apply(serviceComboBox);
                Date runDate = localDateDateFunction.apply(runDatePicker.getValue());
                Date startTime = localTimeFunction.apply(new Pair<>(runDate, startTimePicker.getValue()));
                Date endTime = localTimeFunction.apply(new Pair<>(runDate, endTimePicker.getValue()));
                String keywordType = keywordComboBox.getValue().getText();
                String keyword = keywordText.getText();
                SearchSourceBuilder searchBuilder = new SearchSourceBuilder();
                BoolQueryBuilder bool = QueryBuilders.boolQuery();
                term("platform", platform, bool);
                term("taskType", taskType, bool);
                term("finish", finish == null ? null : (finish == 1 ? true : false), bool);
                term("status", status, bool);
                term("source", source, bool);
                term("serviceName", serviceName, bool);
                term(keywordMap.get(keywordType), keyword, bool);
                term("runDate", Integer.valueOf(new SimpleDateFormat("yyyyMMdd").format(runDate)), bool);

                bool.filter(QueryBuilders.rangeQuery("startTime")
                        .gte(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTime))
                        .lte(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(endTime)));
                searchBuilder.query(bool);
                try {
                    String indexName = "dkd_chain_monitor_task_record_" + DateUtil.format(new Date(), "YYYY_MM");
                    JSONObject searchHits = Es7Client.searchPage(searchBuilder, 1, indexName);

                    tableViewId.getChildren().clear();
                    paginationId.getChildren().clear();
                    TableView<JSONObject> tableView = new TableView<>();
                    tableView.getItems().addAll(listFunction.apply(searchHits.getJSONArray("data")));
                    for (TableViewEnum.TableTitle tableTitle : TableViewEnum.TableTitle.values()) {
                        TableColumn tableColumn = new TableColumn();
                        tableColumn.setVisible(!tableTitle.isHide());
                        tableColumn.setSortable(tableTitle.isSort());
                        tableColumn.setText(tableTitle.getTitle());
                        tableColumn.setId(tableTitle.getCode());
                        tableColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures, ObservableValue>) cellDataFeatures -> {
                            JSONObject data = (JSONObject) cellDataFeatures.getValue();
                            SimpleStringProperty p = new SimpleStringProperty(tableTitle.getMapper().apply(new Pair<>(data, tableTitle.getCode())));
                            return p;
                        });
                        tableView.getColumns().add(tableColumn);
                    }
                    TableColumn tableColumn = new TableColumn();
                    tableColumn.setId("operation");
                    tableColumn.setText("操作");
                    tableColumn.setSortable(false);
                    tableColumn.setCellFactory((Callback<TableColumn, TableCell>) param -> new TableCellOperation((Consumer<HBox>) hBox -> {
                        JFXButton button = new JFXButton("查看");
                        button.setButtonType(JFXButton.ButtonType.RAISED);
                        button.setPrefWidth(200);

                        button.setOnAction(new EventHandler<ActionEvent>() {
                            @Override
                            public void handle(ActionEvent event1) {
                                JFXButton bt = (JFXButton) event1.getSource();
                                TableRow tableRow = (TableRow) bt.getParent().getParent().getParent();
                                JSONObject data = (JSONObject) tableRow.getItem();
                                alertText(data, bt);
                            }

                            private void alertText(JSONObject data, JFXButton bt) {
                                JFXAlert alert = new JFXAlert(bt.getScene().getWindow());
                                alert.initModality(Modality.WINDOW_MODAL);
                                alert.setOverlayClose(false);
                                JFXDialogLayout layout = new JFXDialogLayout();
                                layout.setHeading(new Label("内容"));
                                JFXTextArea textArea = new JFXTextArea();

                                textArea.setWrapText(true);
                                textArea.setEditable(false);
                                textArea.setMinWidth(1300);
                                //textArea.setMinHeight(800);
                                textArea.setStyle("-fx-font-size: 15px;-fx-text-fill: #14306b;-fx-background-color: #ffffff");
                                layout.setMinWidth(1300);
                                layout.setMinHeight(800);
                                textArea.setText(new org.json.JSONObject(JSONObject.toJSONString(data)).toString(4));
                                layout.setBody(textArea);


                                JFXButton closeButton = new JFXButton("关闭");
                                closeButton.getStyleClass().add("dialog-accept");
                                closeButton.setOnAction(e -> alert.hideWithAnimation());
                                layout.setActions(closeButton);
                                layout.setMinWidth(1300);
                                layout.setMaxHeight(800);
                                alert.getDialogPane().setStyle(".jfx-alert-content-container{ -fx-background-color: #2d2c2c;}");
                                alert.setContent(layout);
                                alert.setWidth(1300);
                                alert.setHeight(800);
                                alert.show();
                            }
                        });


                        JFXButton flowButton = new JFXButton("流程");
                        flowButton.setButtonType(JFXButton.ButtonType.RAISED);
                        flowButton.setPrefWidth(200);
                        hBox.getChildren().addAll(button, flowButton);


                        flowButton.setOnMouseClicked(e -> {
                            JFXButton b = (JFXButton) e.getSource();
                            TableRow tableRow = (TableRow) b.getParent().getParent().getParent();
                            JSONObject data = (JSONObject) tableRow.getItem();
                            HBox flowBox = new HBox();
                            Task task = taskMapper.apply(data);
                            Date s1 = task.getStepList().get(0).getBegin();
                            startTimeThreadLocal.set(s1);
                            flowBox.getChildren().clear();
                            List<Step> stepList = task.getStepList();
                            int stepIndex = 1;
                            for (Step step : stepList) {
                                JFXButton bfx = buildButton(step.getRemark(), 80, "#ffffff", "#2d2c2c", step.getStatus(), step.getStage());
                                bfx.setId(step.getTaskId() + stepIndex);
                                stepIndex += 1;
                                VBox vBox = new VBox();
                                vBox.setAlignment(Pos.TOP_CENTER);
                                vBox.getChildren().add(bfx);
                                vBox.setUserData(step);
                                List<StepNode> stepNodes = step.getStepNodes();
                                addStepNodeButton(stepNodes, vBox, 1, true);
                                flowBox.getChildren().add(vBox);
                                flowBox.getChildren().add(buildPaddingLabel(200, 20));
                            }
                            flowBox.setPrefSize(1800, 800);
                            flowBox.setMinWidth(1800);
                            flowBox.setMinHeight(800);
                            //JFXPopup popup = new JFXPopup(flowBox);
                            //popup.setPrefSize(1800,800);
                            //popup.show(flowButton.getScene().getWindow(),0,0, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT,0,0);

                            buildAlertWindow(flowBox, b.getScene().getWindow());
                        });

                    }));
                    tableView.getColumns().add(tableColumn);

                    tableViewId.getChildren().add(tableView);
                    buildPagination(searchHits.getLong("total"), tableView, searchBuilder, indexName);


                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    private Function<JSONArray, List<JSONObject>> listFunction = searchHits1 -> {
        List<JSONObject> jsonObjectList = new ArrayList<>();
        for (Object hit : searchHits1) {
            JSONObject hitObj = (JSONObject) hit;
            processRow(hitObj);
            jsonObjectList.add(hitObj);
        }
        return jsonObjectList;
    };

    private JSONObject processRow(JSONObject hitObj) {
        Integer taskType = hitObj.getInteger("taskType");
        hitObj.put("foodName", "");
        if (taskType.equals(8)) {
            String taskParam = hitObj.getString("taskParam");
            JSONObject data = JSONObject.parseObject(taskParam);
            if (!setLocalName(data, hitObj)) {
                setSpuName(data, hitObj);
            }
        }
        return hitObj;
    }

    private static final Map<String,String> LOCAL_SPU_NAME_MAP = new ConcurrentHashMap<>();

    private static final Map<String,String> SPU_NAME_MAP = new ConcurrentHashMap<>();

    private List<JSONObject> processRows(JSONArray ds) {
        int size = ds.size();
        List<JSONObject> rows = new ArrayList<>();
        if (size > 0) {
            Set<String> localSpuIds = new HashSet<>();
            Set<String> spuIds = new HashSet<>();
            for (int i = 0; i < ds.size(); i++) {
                JSONObject jsonObject = ds.getJSONObject(i);
                String taskParam = jsonObject.getString("taskParam");
                JSONObject data = JSONObject.parseObject(taskParam);
                String localSpuId = data.getJSONObject("task").getString("localSpuId");
                String spuId = data.getJSONObject("task").getString("spuId");
                jsonObject.put("foodName","");
                if (StringUtils.isNotBlank(localSpuId) ) {
                    if(!LOCAL_SPU_NAME_MAP.containsKey(localSpuId)) {
                        localSpuIds.add(localSpuId);
                    }else{
                        jsonObject.put("foodName",LOCAL_SPU_NAME_MAP.get(localSpuId));
                    }
                }
                if (StringUtils.isNotBlank(spuId)) {
                    if(!SPU_NAME_MAP.containsKey(spuId)) {
                        spuIds.add(spuId);
                    }else{
                        if(StringUtils.isEmpty(jsonObject.getString("foodName"))){
                            jsonObject.put("foodName",SPU_NAME_MAP.get(spuId));
                        }
                    }
                }
                jsonObject.put("spuId",Optional.ofNullable(spuId).orElse(""));
                jsonObject.put("localSpuId",Optional.ofNullable(localSpuId).orElse(""));


                rows.add(jsonObject);
            }
            Map<String, String> localFoodNameMap = null;
            Map<String, String> spuNameMap = null;
            if(!localSpuIds.isEmpty()){
                localFoodNameMap = JdbcUtils.queryLocalFoodNames(localSpuIds);
            }
            if(!spuIds.isEmpty()){
                spuNameMap = findSpuNameMap(spuIds);
            }
            for (JSONObject row : rows) {
                String localSpuId = row.getString("localSpuId");
                String name = null;
                if(StringUtils.isNotBlank(localSpuId) && localFoodNameMap!=null){
                    name = localFoodNameMap.get(localSpuId);
                    if(StringUtils.isNotBlank(name)){
                        LOCAL_SPU_NAME_MAP.put(localSpuId,name);
                    }
                }
                String spuId = row.getString("spuId");
                if(StringUtils.isEmpty(name) && StringUtils.isNotBlank(spuId) && spuNameMap!=null){
                    name = spuNameMap.get(spuId);
                    if(StringUtils.isNotBlank(name)){
                        SPU_NAME_MAP.put(spuId,name);
                    }
                }
                if(StringUtils.isNotBlank(name)){
                    row.put("foodName",name);
                }
            }

        }

        return rows;
    }

    private boolean setLocalName(JSONObject data, JSONObject hitObj) {
        String localSpuId = data.getJSONObject("task").getString("localSpuId");
        if (StringUtils.isNotBlank(localSpuId)) {
            String localFoodName = JdbcUtils.queryLocalFoodName(localSpuId);
            if (StringUtils.isNotBlank(localFoodName)) {
                hitObj.put("foodName", localFoodName);
                return true;
            }
        }
        return false;
    }

    private void setSpuName(JSONObject data, JSONObject hitObj) {
        String spuId = data.getJSONObject("task").getString("spuId");
        if (StringUtils.isNotBlank(spuId)) {
            try {
                SearchHits searchHits = Es7Client.searchList(new SearchSourceBuilder().query(QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("spuId", spuId))).size(1)
                        , "dkd_shop_food");
                long total = searchHits.getTotalHits().value;
                if (total > 0) {
                    SearchHit st = searchHits.getHits()[0];
                    String name = Optional.ofNullable(st.getSourceAsMap().get("name")).orElse("").toString();
                    if (StringUtils.isNotBlank(name)) {
                        hitObj.put("foodName", name);
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private Map<String,String> findSpuNameMap(Collection<String> spuIds) {
        Map<String,String> emptyMap  = new HashMap<>();
        for (String localSpuId : spuIds) {
            emptyMap.put(localSpuId,"");
        }
        try {
            SearchHits searchHits = Es7Client.searchList(new SearchSourceBuilder().query(QueryBuilders.boolQuery().filter(QueryBuilders.termsQuery("spuId", spuIds)))
                    .fetchSource(new String[]{"spuId","name"},null).size(1000)
                    , "dkd_shop_food");
            long total = searchHits.getTotalHits().value;
            if (total > 0) {
                for (SearchHit searchHit : searchHits) {
                    Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                    Object spuId = sourceAsMap.get("spuId");
                    Object name = sourceAsMap.get("name");
                    if(name!=null && StringUtils.isNotBlank(name.toString())){
                        emptyMap.put(spuId.toString(),name.toString());
                    }
                }
            }
        } catch (IOException e) {

        }
        return emptyMap;
    }

    private int computePage(long total, int pageSize) {
        long pageNum = total / 10;
        if (pageNum * 10 < total) {
            pageNum += 1;
        }
        return Long.valueOf(pageNum).intValue();
    }

    private Pagination buildPagination(long total, TableView<JSONObject> tv, SearchSourceBuilder searchBuilder, String indexName) {
        Pagination pagination = new Pagination();
        pagination.setId("taskPageId");
        pagination.getStyleClass().add("pagination_css");

        pagination.setPageCount(computePage(total, 10));
        pagination.setCurrentPageIndex(0);
        pagination.setPageFactory(param -> {
            try {
                tv.getItems().clear();
                JSONObject page = Es7Client.searchPage(searchBuilder,
                        param + 1,
                        indexName);
                JSONArray ds = page.getJSONArray("data");
                for (JSONObject newRow : processRows(ds)) {
                    tv.getItems().add(newRow);
                    System.out.println(newRow);
                }
                pagination.setPageCount(computePage(total, 10));
                pagination.setCurrentPageIndex(param);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return new Label();
        });
        paginationId.getChildren().add(pagination);
        return pagination;
    }


    public javafx.scene.control.Label buildPaddingLabel(int width, int height) {
        javafx.scene.control.Label label = new javafx.scene.control.Label();
        label.setPrefSize(width, height);
        return label;
    }

    private void addStepNodeButton(List<StepNode> stepNodeList, VBox parent, int pageIndex, boolean next) {
        SimpleDateFormat yyyyMMddHHmmss = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        int nodeSize = stepNodeList.size();
        boolean markIndex = nodeSize > 1;
        Step step = (Step) parent.getUserData();
        if (nodeSize <= 3) {
            addStepNodeByIndex(0, Math.min(nodeSize, 3), parent, step, markIndex, yyyyMMddHHmmss);
        } else {
            int startIndex = Math.max(pageIndex * 3 - 3, 0);
            int endIndex = Math.min(startIndex + 3, nodeSize);
            addStepNodeByIndex(startIndex, endIndex, parent, step, markIndex, yyyyMMddHHmmss);
        }
        if (nodeSize > 3) {
            parent.getChildren().add(buildPaddingLabel(10, 50));
            //vBox.setStyle("-fx-text-fill: #16e07b");
            JFXButton nextLog = buildButton(next ? "next" : "before", 50, "#ffffff", "#2d2c2c", 1, step.getStage());
            StepNodeEntity stepNodeEntity = new StepNodeEntity();
            stepNodeEntity.setStepNodeList(stepNodeList);
            stepNodeEntity.setPage(pageIndex);

            if (next && pageIndex * 3 < nodeSize) {
                stepNodeEntity.setNext(true);
            } else if (!next && pageIndex <= 0) {
                stepNodeEntity.setNext(true);
            } else {
                stepNodeEntity.setNext(false);
            }
            nextLog.setUserData(stepNodeEntity);
            nextLog.setOnAction(event -> {
                JFXButton eventSource = (JFXButton) event.getSource();
                StepNodeEntity userData = (StepNodeEntity) eventSource.getUserData();
                StepNode stepNode = userData.getStepNodeList().get(0);
                String tid = stepNode.getTaskId();

                VBox vBox = (VBox) eventSource.getParent();
                ObservableList<Node> children = vBox.getChildren();
                List<Integer> removeIndex = new ArrayList<>();
                for (int i = 0; i < children.size(); i++) {
                    Node node = children.get(i);
                    if (node instanceof JFXButton) {
                        String id = node.getId();
                        if (StringUtils.isEmpty(id) || !id.startsWith(tid)) {
                            removeIndex.add(i);
                        }
                    } else {
                        removeIndex.add(i);
                    }
                }
                for (int index : removeIndex.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList())) {
                    vBox.getChildren().remove(index);
                }
                addStepNodeButton(stepNodeList, vBox, userData.nextPage(), userData.isNext());
            });
            parent.getChildren().add(nextLog);
        }
    }

    public void addStepNodeByIndex(int startIndex, int endIndex, VBox parent, Step step, boolean markIndex, SimpleDateFormat yyyyMMddHHmmss) {
        int nodeIndex = startIndex + 1;

        for (StepNode stepNode : step.getStepNodes().subList(startIndex, endIndex)) {
            parent.getChildren().add(buildPaddingLabel(10, 50));
            JFXButton stepLog = buildButton(markIndex ? ("任务" + nodeIndex) : "任务", 50, "#ffffff", "#2d2c2c", stepNode.getStatus(), step.getStage());
            //stepLog.setAlignment(Pos.TOP_CENTER);
            stepLog.setId(stepNode.getLogId());

            buildButtonAction(stepNode, stepLog, step, yyyyMMddHHmmss);

            parent.getChildren().add(stepLog);
            nodeIndex++;
        }
    }

    private void buildButtonAction(StepNode stepNode, JFXButton stepLog, Step step, SimpleDateFormat yyyyMMddHHmmss) {
        stepLog.setUserData(JSONObject.toJSONString(stepNode));
        stepLog.setOnAction(event -> {
            JFXButton bt = (JFXButton) event.getSource();
            String userData = (String) bt.getUserData();
            StepNode node = JSONObject.parseObject(userData, StepNode.class);
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd");
            SearchSourceBuilder searchBuilder = new SearchSourceBuilder();
            searchBuilder.query(QueryBuilders.boolQuery()
                            .filter(QueryBuilders.termQuery("logId", node.getLogId())))
                    .size(9000)
                    .sort(
                            Arrays.asList(
                                    new FieldSortBuilder("timestamp").order(SortOrder.ASC),
                                    new FieldSortBuilder("id").order(SortOrder.ASC))
                    )
            ;
            try {
                StringBuilder logMsg = new StringBuilder();
                SearchHits searchHits = Es7Client.searchList(searchBuilder, "dkd_chain_monitor_log_record_" + simpleDateFormat.format(startTimeThreadLocal.get()));
                for (SearchHit hit : searchHits.getHits()) {
                    JSONObject log = JSONObject.parseObject(hit.getSourceAsString());

                    String[] msgArr = log.get("msg").toString().split("#msg:");
                    logMsg.append(
                                    yyyyMMddHHmmss.format(new Date(log.getLong("timestamp"))))
                            .append("\t")
                            .append(msgArr[0])
                            .append("\t")
                            .append(msgArr.length > 1 ? msgArr[1] : "")
                            .append("\n")
                            .append("\n")
                    ;


                }
                buildAlertWindow(logMsg.toString(), bt.getScene().getWindow());

            } catch (IOException e) {
                throw new RuntimeException(e);
            }


        });


        String serviceName = step.getServiceName();
        String remark = step.getRemark();
        Date begin = step.getBegin();
        Date end = step.getEnd();
        StringBuilder builder = new StringBuilder();
        builder.append("服务:").append(serviceName)
                .append("\n")
                .append("备注:").append(remark)
                .append("\n")
                .append("开始时间:").append(yyyyMMddHHmmss.format(begin))
                .append("\n")
                .append("结束时间:").append(yyyyMMddHHmmss.format(end))
                .append("\n")
                .append("消耗时间:(s)").append(step.getCost() / 1000)
                .append("\n")
                .append("result:").append(Optional.ofNullable(stepNode.getResult()).orElse("")
                );

        stepLog.setTooltip(new Tooltip(builder.toString()));
    }

    private void buildAlertWindow(Node node, Window window) {
        JFXAlert alert = new JFXAlert(window);
        alert.initModality(Modality.WINDOW_MODAL);
        alert.setOverlayClose(false);
        JFXDialogLayout layout = new JFXDialogLayout();
        layout.setHeading(new Label("日志"));
        layout.setBody(node);
        JFXButton closeButton = new JFXButton("关闭");
        closeButton.getStyleClass().add("dialog-accept");
        closeButton.setOnAction(e -> alert.hideWithAnimation());
        layout.setActions(closeButton);
        layout.setMinWidth(1300);
        layout.setMaxHeight(800);
        alert.getDialogPane().setStyle(".jfx-alert-content-container{ -fx-background-color: #2d2c2c;}");
        alert.setContent(layout);
        alert.setWidth(1300);
        alert.setHeight(800);
        alert.show();
    }

    private void buildAlertWindow(String logMsg, Window window) {
        JFXAlert alert = new JFXAlert(window);
        alert.initModality(Modality.WINDOW_MODAL);
        alert.setOverlayClose(false);
        JFXDialogLayout layout = new JFXDialogLayout();
        layout.setHeading(new Label("日志"));
        JFXTextArea textArea = new JFXTextArea();

        textArea.setWrapText(true);
        textArea.setEditable(false);
        textArea.setMinWidth(1300);
        //textArea.setMinHeight(800);
        textArea.setStyle("-fx-font-size: 15px;-fx-text-fill: #14306b;-fx-background-color: #ffffff");
        layout.setMinWidth(1300);
        layout.setMinHeight(800);
        textArea.setText(logMsg);
        layout.setBody(textArea);


        JFXButton closeButton = new JFXButton("关闭");
        closeButton.getStyleClass().add("dialog-accept");
        closeButton.setOnAction(e -> alert.hideWithAnimation());
        layout.setActions(closeButton);
        layout.setMinWidth(1300);
        layout.setMaxHeight(800);
        alert.getDialogPane().setStyle(".jfx-alert-content-container{ -fx-background-color: #2d2c2c;}");
        alert.setContent(layout);
        alert.setWidth(1300);
        alert.setHeight(800);
        alert.show();
    }

    private JFXButton buildButton(String text, int radius, String textFill, String backgroundFill, Integer status, Integer stage) {
        JFXButton button = new JFXButton();
        button.setText(text);
        button.setWrapText(true);
        //prefWidth="100" prefHeight="40" buttonType="RAISED"
        button.setPrefSize(radius * 2, radius * 2);
        button.setButtonType(JFXButton.ButtonType.RAISED);
        Circle circle = new Circle(radius);
        button.setShape(circle);
        button.setTextFill(Paint.valueOf(textFill));
        if (status < 1) {
            backgroundFill = "#e02716";
        }

        button.setBackground(new Background(new BackgroundFill(Paint.valueOf(backgroundFill), null, null)));


        button.setStyle("-fx-font-size:14px;");
        BorderWidths DEFAULT = new BorderWidths(3, 3, 3, 3, false, false, false, false);
        String borderColor = "#e83972";
        if (stage == 1) {
            borderColor = "#16e07b";
        }
        if (stage == 2) {
            borderColor = "#1656e0";
        }
        BorderStroke borderStroke = new BorderStroke(Paint.valueOf(borderColor), BorderStrokeStyle.DASHED, null, DEFAULT);
        button.setBorder(new Border(borderStroke));
        return button;
    }

    private void term(String key, Object value, BoolQueryBuilder bool) {
        if (value == null) {
            return;
        }
        if (value instanceof String && StringUtils.isEmpty(value.toString())) {
            return;
        }
        bool.filter(QueryBuilders.termQuery(key, value));
    }

    public static ObservableList<Node> getChildren(Class<?> clazz, Object instance) {
        Method getChildren = null;
        try {
            Class<?> a = clazz;
            while (true) {
                try {
                    getChildren = a.getDeclaredMethod("getChildren");
                    if (getChildren != null) {
                        break;
                    }
                } catch (Exception e) {
                    a = a.getSuperclass();
                }
            }


            getChildren.setAccessible(true);
            Object result = getChildren.invoke(instance);
            return (ObservableList<Node>) result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
